#include "state_intro.h"
#include "krryn.h"
#include "md5_builder.h"
#include "obj_loader.h"
#include "debug_helper.h"

#include "mesh.h"
#include "model.h"
#include <fstream>
#include <cassert>

using namespace krryn::math;
using namespace krryn::system;
using namespace krryn::system_imp;
using namespace krryn::usr;
using namespace krryn::core;
using namespace krryn::scene;
using namespace krryn::view;
using namespace krryn::gui;
using namespace krryn::parsers;
using namespace krryn::data;

static state_intro _state_intro;

//md5_builder<software_tag> b("data/models/monsters/teleport_dropper/teleport_dropper.md5mesh");
model<software_tag> modx, mod1;
default_renderer<software_tag> dr(0);

template<typename _Type>
struct is_int
{
	static const bool m_Value = false;
};

template<>
struct is_int<int>
{
	static const bool m_Value = true;
};

template<typename _VertexType>
void dump_model_raw(model<software_tag, _VertexType> &model, const char *a_Filename)
{
	FILE *f;
	
	errno_t e = fopen_s(&f,a_Filename, "wb");

	if(e) return;

	// version number
	int version = 4;
	fwrite(&version, 1, sizeof(unsigned int), f); 

	// write down the binary format (fixed point ints or floats)
	bool isint = is_int<vertex_buffer<software_tag, _VertexType>::type>::m_Value;
	fwrite(&isint, 1, sizeof(bool), f);

	// amount of meshes (uint32)
	size_t nummeshes = model.m_Meshes.size();
	fwrite(&nummeshes, 1, sizeof(size_t), f);

	for(size_t i = 0; i < model.m_Meshes.size(); i++)
	{
		size_t vb_size = (char*)model.m_Meshes[i]->get_vertex_buffer()->end() - (char*)model.m_Meshes[i]->get_vertex_buffer()->begin();
		size_t ib_size = (char*)model.m_Meshes[i]->get_index_buffer()->end() - (char*)model.m_Meshes[i]->get_index_buffer()->begin();

		fwrite(&vb_size, 1, sizeof(size_t), f);
		fwrite(&ib_size, 1, sizeof(size_t), f);

		vb_size = model.m_Meshes[i]->get_vertex_buffer()->size();
		ib_size = model.m_Meshes[i]->get_index_buffer()->size();

		fwrite(&vb_size, 1, sizeof(size_t), f);
		fwrite(&ib_size, 1, sizeof(size_t), f);
	}

	for(size_t i = 0; i < model.m_Meshes.size(); i++)
	{
		// don't serialize textures without models, we can't render them anyway
		size_t s = 0;
		if(!model.m_Meshes[i]->effects().front())
		{
			fwrite(&s, 1, sizeof(size_t), f);
		}
		else
		{
			std::string name = model.m_Meshes[i]->effects().front()->get_name();
			s = name.size();
			fwrite(&s, 1, sizeof(size_t), f);
			fwrite(name.c_str(), 1, name.size(), f);
		}

		fwrite(
			model.m_Meshes[i]->get_vertex_buffer()->begin(), 
			1,
			(char*)model.m_Meshes[i]->get_vertex_buffer()->end() - (char*)model.m_Meshes[i]->get_vertex_buffer()->begin(),
			f);

		fwrite(
			model.m_Meshes[i]->get_index_buffer()->begin(), 
			1,
			(char*)model.m_Meshes[i]->get_index_buffer()->end() - (char*)model.m_Meshes[i]->get_index_buffer()->begin(),
			f);
	}

	fclose(f);
}

template<typename _VertexType>
int read_model_raw(model<software_tag, _VertexType> &model, renderer<software_tag> &a_Renderer, const char *a_Filename){
	FILE *f;
	errno_t e = fopen_s(&f, a_Filename, "rb");

	if(e) return -1;

	int version = 0, thisversion = 4;
	bool isint = 0;
	size_t nummeshes = 0;

	fread(&version, 1, sizeof(int), f);
	fread(&isint, 1, sizeof(bool), f);
	fread(&nummeshes, 1, sizeof(size_t), f);

	if(version != thisversion) return -2;
	bool t = is_int<vertex_buffer<software_tag, _VertexType>::type>::m_Value;
	if(isint != t) return -3;

	model.m_Meshes.resize(nummeshes);

	std::vector<size_t> vsizes, isizes;

	for(size_t i = 0; i < model.m_Meshes.size(); i++)
	{
		size_t vb_sizebyte = 0, vb_size = 0;
		size_t ib_sizebyte = 0, ib_size = 0;

		fread(&vb_sizebyte, 1, sizeof(size_t), f);
		fread(&ib_sizebyte, 1, sizeof(size_t), f);

		fread(&vb_size, 1, sizeof(size_t), f);
		fread(&ib_size, 1, sizeof(size_t), f);

		model.m_Meshes[i] = new krryn::scene::mesh<software_tag, _VertexType>(
			new vertex_buffer<software_tag, _VertexType>(a_Renderer, vb_size),
			new index_buffer<software_tag>(a_Renderer, ib_size));

		vsizes.push_back(vb_sizebyte);
		isizes.push_back(ib_sizebyte);
	}

	std::map<std::string, texture_software *> textures;

	for(size_t i = 0; i < model.m_Meshes.size(); i++)
	{
		size_t s;
		fread(&s, 1, sizeof(size_t), f);

		if(s != 0)
		{
			char name[1000];
			fread(name, 1, s, f);
			name[s] = 0;

			if(!textures[name])
			{
				canvas *c = new canvas(name);
				c->vflip();

				texture_software *t = new texture_software(c, 0);
				t->set_name(name);
				textures[name] = t;
			}

			model.m_Meshes[i]->effects().push_back(textures[name]);
		}

		size_t vbsize = fread(model.m_Meshes[i]->get_vertex_buffer()->begin(), 1, vsizes[i], f);
		size_t ibsize = fread(model.m_Meshes[i]->get_index_buffer()->begin(), 1, isizes[i], f);

		if(vbsize != vsizes[i]) { printf("invalid vertexbuffer size"); return -6; }
		if(ibsize != isizes[i]) { printf("invalid indexbuffer size"); return -7; }
	}

	fclose(f);
	return 1;
}

template<typename _VertexType>
class rasterize_thread : public thread{
public:
	rasterize_thread(event &renderDone, event &blitDone, std::vector<krryn::scene::mesh<software_tag, _VertexType>* > &meshes, renderer<software_tag> &rasterizer)
		: m_RenderDone(renderDone), m_Meshes(meshes), m_Rasterizer(rasterizer), m_BlitDone(blitDone), f(90) {}

	krryn::view::canvas *get_canvas() { return m_Rasterizer.get_canvas(); }
	typename renderer<software_tag>::data_type *get_depth_buffer() { return m_Rasterizer.get_depth_buffer(); }

	virtual void run(){
		while(true)
		{
			m_BlitDone.wait();

			matrix m;

			static float s = 100.f;

			m.scale(vector3f(s, -s, s));
			m.set_translation(vector3f(0, 2, 0));

			{
				f += 0.6;
				//printf("%.2f\n", f);
				matrix t;
				t.rotate_y(f);
				m.concatenate(t);
			}

			m_Rasterizer.begin_scene();

			for(size_t i = 0; i < m_Meshes.size(); i++)
			{
				m_Rasterizer.draw(*m_Meshes[i], m);
			}

			m_Rasterizer.end_scene();

			m_RenderDone.trigger();
		}
	}
private:
	float f;
	std::vector<krryn::scene::mesh<software_tag, _VertexType>* > &m_Meshes;
	renderer<software_tag> &m_Rasterizer;
	event &m_RenderDone, &m_BlitDone;
};

namespace {
	event done[] = {event("render thread 1 done"), event("render thread 2 done")};
	event blitdone[] = {event("blit thread 1 done"), event("blit thread 2 done")};
	renderer<software_tag> *extraRenderer[2];
	rasterize_thread<krryn::scene::vertex> *rthread[2];
	std::vector<krryn::scene::mesh<software_tag, krryn::scene::vertex>* > meshes[2];
}

state_intro::~state_intro(){}

template<typename _VertexType>
void load_cached_obj(const char *path, const char *filename, model<software_tag, _VertexType> &mod)
{
	std::string p(path);
	std::string f(filename);
	std::string both = p + f;
	std::string cached = both + ".krrynraw" + (is_int<vertex_buffer<software_tag, _VertexType>::type>::m_Value ? "i" : "f");

	if(read_model_raw(mod, *dr.m_Renderer, cached.c_str()) < 0)
	{
		obj_loader<software_tag> obj(both.c_str());
		mod = obj.load(*dr.m_Renderer);
		dump_model_raw(mod, cached.c_str());
	}
}

void state_intro::init(){
//	mod = b.build_model(*dr.m_Renderer);

	load_cached_obj("data/buildings/", "sponza_triangulated.obj", mod1);

	size_t h = 0;
	for(; h < mod1.m_Meshes.size() / 2; h++){
		mod1.m_Meshes[h]->update_aabb();
		meshes[0].push_back(mod1.m_Meshes[h]);
	}

	for(; h < mod1.m_Meshes.size(); h++){
		mod1.m_Meshes[h]->update_aabb();
		meshes[1].push_back(mod1.m_Meshes[h]);
	}

	char n[] = "Render thread 0";

	for(int i = 0; i < 2; i++){
		size s = dr.m_RenderControl->get_size();
		canvas *c = new canvas(s.m_Width, s.m_Height);
		c->set_pitch(c->get_width());
		extraRenderer[i] = new krryn::system::renderer<software_tag>(0, c);

		std::cout << "Rendering " << meshes[i].size() << " meshes on core " << i << std::endl;

		rthread[i] = new rasterize_thread<krryn::scene::vertex>(done[i], blitdone[i], meshes[i], *extraRenderer[i]);
		rthread[i]->start();
		n[strlen(n) - 1]++;
		rthread[i]->set_name(n);
		rthread[i]->run_on_core(i);
	}

	dr.m_Renderer->begin_scene();
	dr.m_Renderer->get_canvas()->clear(0xFF00FF);
	dr.m_Renderer->end_scene();

	for(int i = 0; i < 2; i++){
		blitdone[i].trigger();
	}

	notify();

	std::cout << "started rendering " << std::endl;
}

void state_intro::activate(){}

void state_intro::deactivate(){}

void state_intro::tick(float a_DeltaTime){
	wait_multiple_events(done, 2);

	dr.m_Renderer->begin_scene();

	view::canvas * dst = dr.m_Renderer->get_canvas();
	view::canvas * src[] = {rthread[0]->get_canvas(), rthread[1]->get_canvas() };
	renderer<software_tag>::data_type * depth[] = {rthread[0]->get_depth_buffer(), rthread[1]->get_depth_buffer() };

	dst->set_buffer(src[0]->get_buffer());

	for(int y = 0; y < src[0]->get_height(); y++){
		for(int x = 0; x < src[0]->get_width(); x++){
			int pos = x + y * dst->get_pitch();
			int dpos = x + y * dst->get_width();
#if 1
			renderer<software_tag>::data_type value 
				= fp_values<renderer<software_tag>::data_type>::zero();

			int idx = -1;
			for(int d = 0; d < 2; d++)
				if(depth[d][dpos] >= value){
					idx = d;
					value = depth[d][dpos];
				}
#else
			int idx = -1;
			for(int d = 0; d < 2; d++)
				if(src[d]->get_buffer()[pos]) idx = d;
#endif
			assert(idx != -1);

			dst->get_buffer()[pos] = src[idx]->get_buffer()[pos];
		}
	}

	//static char name0[] = {"data/wa1w_0.bmp"};
	//static char name1[] = {"data/wo2w_0.bmp"};

	//src[0]->store(name0);
	//src[1]->store(name1);

	//name0[strlen("data/wauw_")]++;
	//name1[strlen("data/wauw_")]++;

	dr.m_Renderer->end_scene();

	static float total = 0;
	static float count = 0;

	count ++;
	total += a_DeltaTime;

	std::stringstream ss;
	ss << std::setprecision(3) << "average ms: " << ((total / count) * 1000.f) << " current ms: " << (a_DeltaTime * 1000.f);
	dr.m_Label->set_text(ss.str());

	if(count > 100){
		count = total = 0;
	}

	m_EventLoop.update();
	
	for(int i = 0; i < 2; i++){
		blitdone[i].trigger();
	}
}